import { timeFormat } from '../../renderer/assets/time.js'

// 获取mac地址
import getMAC, { isMAC } from 'getmac'
// MD5 插件
import md5 from 'md5-nodejs'
import { BACKBONE_DEBUGGER } from 'electron-devtools-installer'

const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const koa = require('koa')
const Router = require('koa-router')
const fs = require('fs') // node原生模块 文件处理
const os = require('os') // node原生模块
const path = require('path') // node原生模块
const koa_static = require('koa-static-server')
const koaBody = require('koa-body') // koa body中间件
const logger = require('koa-logger') // log日志插件
const crypto = require('crypto') // 加密模块
const mammoth = require('mammoth') // word转换 不带样式
const exec = require('child_process').exec;
// 基础表
const adapter = new FileSync(path.join(__dirname, '../../../static/db/db.json'), {
  serialize: (data) => JSON.stringify(data),
  deserialize: (data) => JSON.parse(data)
})

// 菜单角色表
const adapterMenu = new FileSync(path.join(__dirname, '../../../static/db/menu.json'), {
  serialize: (data) => JSON.stringify(data),
  deserialize: (data) => JSON.parse(data)
})

// 模板管理表
const adapterTmpl = new FileSync(path.join(__dirname, '../../../static/db/tmpl.json'), {
  serialize: (data) => JSON.stringify(data),
  deserialize: (data) => JSON.parse(data)
})

const db = low(adapter)
const dbMenu = low(adapterMenu)
const dbTmpl = low(adapterTmpl)

/**
 * db.set("user.name", 'kongzhi').write();
 * get 数据, 然后添加一条数据进去，最后写入文档里面去。
 * db.get('posts').push({'id': 1, 'title': 'welcome to hangzhou' }).write();
 * // 使用 update 更新数据 这里的n 就是 count 的默认值，在db.json中的count默认值为30, 最后写入进去
 *   db.update('count', n => n + 1).write();
 * // 查找数据
   const value = db.get('posts').find({'id': 1}).value();
    lowdb 的API
    1. low(adapter): 它返回一个具有特定属性和功能的 lodash.
    2. db.[...].write()/.value(): write() 方法是写入数据，value()方法是读取数据。
    3. db.getState()/.setState(): 获取/设置数据库的状态。
    4. db._ 数据库的lodash的实列。我们可以使用这个来添加我们自己的函数或者第三方的mixins。比如 lodash-id(https://github.com/typicode/lodash-id)
    // 使用 mixin 混合模式来扩展我们自己的方法
    db._.mixin({
        getSecondData: function(arr) {
            return arr[1];
        }
    });
    // 调用 getSecondData 方法 获取到 posts 第二条数据
    const xx = db.get('posts').getSecondData().value();
    console.log(xx);
    db.has('posts').value();  // 如果有该字段就会返回 true， 否则的话，返回false.
    获取特定字段的值
    db.get('posts').map('id').value(); // 执行完成后会返回 [1, 2]
    获取数量
    db.get('posts').size().value(); // 返回该数组的长度为 2
    获取特定信息的值
    db.get('posts[0].id').value(); // 就会返回 1.
    // 更新信息
    db.get('posts')
    .find({title: 'welcome to hangzhou'})
    .assign({name: 'kongzhi'})
    .write();
    // 返回的消息类似如下
    {
        "posts":[
            {"id":1,"title":"welcome to hangzhou","name":"kongzhi"},
            {"id":2,"title":"welcome to hangzhou"}
        ],
        "user":{"name":"kongzhi"},
        "count":33
    }

    删除信息
    db.get('posts')
    .remove({name: 'xxx'})
    .write();
    移除属性
    db.unset('posts[0].id').write();
    深拷贝
    var xx = db.get('posts').cloneDeep().value();
 */
const app = new koa()
const router = new Router()

app.use(logger()) // 过程日志生成

// 设置返回体大小限制
app.use(koaBody({
  jsonLimit: '10kb', // json传输大小 10kb限制
  multipart: true, // 文件上传时候支持多文件上传
  formidable: {
    keepExtensions: true, // 保持文件的后缀
    maxFieldsSize: 20 * 1024 * 1024, // 文件上传大小 最大限制20M
    onFileBegin: (name, file) => { // 文件上传前的设置
      console.log(`name: ${name}`)
      console.log(file)
    }
  }
}))

// 404 页面
app.use(async function (ctx, next) {
  await next()
  if (ctx.body || !ctx.idempotent) return
  ctx.redirect('/404')
})

// 配置静态文件访问地址
app.use(koa_static({
  rootDir: path.join(__dirname, '../../../static/'), // 实际静态文件的存放地址
  rootPath: '/static/'
}))

// 错误处理
app.use(async function (ctx, next) {
  try {
    await next()
  } catch (err) {
    // 一些错误是自带status属性
    ctx.status = err.status || 500
    ctx.type = 'html'
    ctx.body = '<p>Something <em>exploded</em>, please contact Maru.</p>'
    // 触发事件
    ctx.app.emit('error', err, ctx)
  }
})

// 配置跨域请求
app.use(async (ctx, next) => {
  ctx.set('Access-Control-Allow-Origin', '*')
  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild')
  ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS')
  if (ctx.method == 'OPTIONS') {
    ctx.body = 200
  } else {
    await next()
  }
})

// 添加mac地址校验
app.use(async function (ctx, next) {
  // 获取机器mac地址
  let mac = md5(getMAC())
  console.log('获取的mac地址', mac)
  // 查找数据库 获取mac地址
  let dbMac = db.get('user.mac').value()
  console.log('数据库的mac地址', dbMac)
  if (!0) {
    console.log('MAC地址匹配成功！')
    await next()
  } else {
    console.log('MAC地址匹配失败！')
    ctx.body = errRes('客户端绑定失败！')
  }
})

// 随机ID 生成
function uid () {
  return timeFormat(new Date().getTime(), 'yyyyMMdd_hhmmss_') + Math.random().toString(16).slice(2)
}

// 监听错误事件
app.on('error', function (err) {
  if (process.env.NODE_ENV != 'test') {
    console.log('sent error %s to the cloud', err.message)
    console.log(err)
  }
})

// 配置koa 路由
// 获取当前登录人信息
router.get('/getLoginMess', async (ctx) => {
  let data = db.get('user.current').value()
  if (data) {
    ctx.body = successRes('当前登录人信息！', db.get('user.current').value())
  } else {
    ctx.body = errRes('获取失败！')
  }
})

// 获取字典
router.get('/getFileds', async (ctx) => {
  let data = db.get('field').value()
  if (data) {
    ctx.body = successRes('获取字段信息成功！', data)
  } else {
    ctx.body = errRes('获取失败！')
  }
})

router.post('/submitDict', async (ctx) => {
  // console.log(111111, ctx.request.query)
  // console.log(222222, ctx.request.body)
  let data = ctx.request.body
  // console.log(33333,data.data)
  db.set('field', data.data).write()
  ctx.body = successRes('字典提交成功！', data)
})

// 获取模板列表
router.get('/tmpl/list', async (ctx) => {
  let data = dbTmpl.get('tmpl').value()
  if (data) {
    ctx.body = successRes('获取字段信息成功！', data)
  } else {
    ctx.body = errRes('获取失败！')
  }
})

// 写文件流
async function writeFile(filePath, value) {
  return new Promise((resolve, reject) => {
    let wstream = fs.createWriteStream(filePath, {
      encoding: 'utf8',
      autoClose: true
    })

    wstream.write(value)
    wstream.end()

    wstream.on('error', (err) => { reject(err) });
    wstream.on('finish', () => { resolve(true) });

  })
}

function readFile(path){
  return new Promise((resolve)=>{
    fs.readFile(path, (err, data) => {
      if (err) throw err;
      resolve(data)
    })
  })
}

// 本地环境支持 研究中
function officeToHtml(filePath){
  return new Promise((resolve,reject) => {
    exec('unoconv -f ' + 'html' + ' "' + filePath + '"', function (error, stdout, stderr) {
      if (error) return reject(error)
      var outputFile = (filePath.lastIndexOf('.') > -1 ? filePath.substring(0, filePath.lastIndexOf('.')) : filePath) + '.' + 'html';
      resolve(null, {
        status: 0,
        outputFile: outputFile
      })
    })
  })
}

// word转html
router.post('/wordToHtml', async (ctx) => {
  let { url, cl } = ctx.request.body
  let filePath = path.join(__dirname, '../../../static/', url.replace(/index\.(doc|docx)$/i, ''))
  console.log('开始转换html，参数', url)
  if (!url) {
    ctx.body = errRes('参数错误！', null)
  } else {
    let fileArrayBuffer = await readFile(path.join(__dirname, '../../../static/', url))
    console.log('文件buffer', fileArrayBuffer)
    let result = await mammoth.convertToHtml({ buffer: fileArrayBuffer})
    // let result = await officeToHtml(path.join(__dirname, '../../../static/', url))
    // console.log(222222,result)
    console.log(4444444,process.env.NODE_ENV)

    let staticPath = process.env.NODE_ENV !== 'development' ? 'http://localhost:9081/static' : '/static/'

    result.value = `
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>word</title>
      </head>
      <body>
        <div id="content">${result.value}</div>
      </body>
      <script>
        function createScript(src,cb){
            var tag=document.createElement("script");
            var head = document.getElementsByTagName("head")[0];
            var bol=false;
            tag.type="text/javascript";
            if(src.indexOf('?') > -1){
                tag.src = src + '&rendom=' + new Date().getTime();
            }else{
                tag.src=src+'?rendom='+ new Date().getTime();
            }
            tag.onload=tag.onreadystatechange=function(){
                if(!bol&&(!tag.readyState || tag.readyState == "loaded" || tag.readyState == "complete")){
                    bol=true;
                    tag.onload=tag.onreadystatechange=null;
                    cb&&cb.call(tag);
                }
            };
            head.appendChild(tag);
        }
        window.addEventListener('message',function(event){
            if(event.data.type === 'exportWord'){
              if(!window.loadSc){
                createScript(event.data.path +'/lib/jquery.min.js',function(){
                    $.getScript(event.data.path  +'/lib/FileSaver.js',function(){
                        $.getScript(event.data.path  +'/lib/jquery.wordexport.js',function(){
                          window.loadSc = true
                          $('#content').wordExport()
                        })
                    })
                })
              }else{
                $('#content').wordExport()
              }
            }
        })
      </script>
      </html>
    `
    let isWriteFlag = await writeFile(filePath + 'index.html', result.value)
    if (isWriteFlag) {
      dbTmpl.get(`tmpl.${cl}.files`)
        .find({ origin: url })
        .assign({ filePath: url.replace(/index\.(doc|docx)$/i, 'index.html') })
        .write()
    }
    ctx.body = successRes(result.messages, url.replace(/index\.(doc|docx)$/i, 'index.html'))
  }
})

// 获取文件的MD5
async function getFileMd5 (reader, writer) {
  // 创建一个MD5文件戳
  let fsHash = crypto.createHash('md5')
  return new Promise(resolve => {
    reader.on('data', function (d) {
      fsHash.update(d)
      console.log('文件数据', d)
      writer.write(d)
    })

    reader.on('end', function () {
      let md5 = fsHash.digest('hex')
      writer.end()
      resolve(md5)
    })

    reader.on('error', function (err) {
      console.log('读取文件失败', err)
    })
  })
}

// 删除文件
async function deleteFile (file) {
  let filePath = path.join(__dirname, '../../../', file)
  console.log('删除的文件路径', filePath)
  return new Promise((resolve) => {
    fs.unlink(filePath, function (err) {
      if (err) resolve(false)
      // 如果没有错误，则文件已成功删除
      resolve(true)
    })
  })
}
// 删除文件夹
async function deleteFileDir (file) {
  let filePath = path.join(__dirname, '../../../', file)
  let dirPath = path.resolve(filePath, '..')
  console.log('删除的文件夹路径', dirPath)
  return new Promise((resolve) => {
    fs.rmdir(dirPath, function (err) {
      if (err) resolve(false)
      // 如果没有错误，则文件已成功删除
      resolve(true)
    })
  })
}

// 获取文件后缀
function getFilesuffix (path) {
  let suffix = ''
  try {
    suffix = path.match(/\.(\w+)$/)[1]
  } catch (error) {

  }
  return suffix
}

// 创建文件夹
async function cmkdir (dir) {
  return new Promise((resolve) => {
    fs.mkdir(dir, { recursive: true }, (err) => {
      if (err) {
        throw err
      } else {
        resolve('ok')
      }
    })
  })
}

// 上传插件
router.post('/upload', async (ctx) => {
  // 创建一个缓存目录用于保存文件
  /**
     * os.tmpdir() 以字符串的形式返回操作系统的默认临时文件目录。
     */
  let id = uid()

  const tmpdir = path.join(__dirname, `../../../static/files/${id}`)
  console.log('文件目录', tmpdir)
  // 创建这个目录
  await cmkdir(tmpdir)
  const filePaths = []
  // console.log(111111, ctx.request.body.files) // 旧版本koa body 采用
  // console.log(222222, ctx.request.files) // 新版本koabody使用
  const files = ctx.request.body.files || {}
  // console.log(2222, ctx.request.body.files);
  let filesPush = []
  for (let key in files) {
    const file = files[key]
    console.log(111111111, file.name)
    const filePath = path.join(tmpdir, file.name)
    const reader = fs.createReadStream(file.path) // 流读取文件
    const writer = fs.createWriteStream(filePath) // 写入文件

    let MD5 = await getFileMd5(reader, writer)
    console.log('获取文件的MD5', MD5)
    // reader.pipe(writer)  // 不生效 问题不明
    filePaths.push(filePath)
    let filsObj = {
      filePath: filePath,
      fileName: file.name,
      MD5: MD5,
      suffix: getFilesuffix(filePath),
      createTime: timeFormat(new Date().getTime(), 'yyyy-MM-dd hh:mm:ss')
    }
    filesPush.push(filsObj)
    // 写入数据库
    db.get('file').push(filsObj).write()
  }
  ctx.body = successRes('上传成功！', filesPush) // 返回文件路径
})

// 获取文件分页
router.post('/getFiles', async (ctx) => {
  // 获取参数
  let data = ctx.request.body
  let pageNum = data.pageNum
  let pageOnce = data.pageOnce
  let start = (pageNum - 1) * pageOnce
  let end = pageNum * pageOnce
  console.log(start + '------' + end)
  let files = Object.assign([], db.get('file').value())
  let total = files.length
  // console.log(22222, files)
  let list = files.splice(start, end)
  // console.log(33333, list)
  ctx.body = successRes('获取数据成功', {
    list: list,
    total: total
  })
})

// 删除文件
router.post('/deleteFile', async (ctx) => {
  // 获取参数
  let data = ctx.request.body
  let filepath = data.filePath
  console.log('开始执行删除文件操作！')
  let file = db.get('file').find({ filePath: filepath }).value()
  console.log('删除的文件信息', file)
  let isDeleteFlag = await deleteFile(file.filePath)
  let isDeleteFlagDir = await deleteFileDir(file.filePath)
  if (isDeleteFlag && isDeleteFlagDir) {
    db.get('file').remove({ filePath: filepath }).write()
    ctx.body = successRes('删除文件成功', file)
  } else {
    ctx.body = errRes('删除文件失败')
  }
})

// 404页面
router.get('/404', async (ctx) => {
  ctx.type = 'html'
  ctx.body = '<p>页面找不到了404！</p>'
})

// 错误信息 统一展示
function errRes (msg = '请求错误') {
  return {
    code: 'err',
    createTime: new Date().getTime(),
    msg: msg
  }
}

// 成功信息 统一展示
function successRes (msg = '请求成功', data) {
  return {
    code: 'ok',
    createTime: new Date().getTime(),
    msg: msg,
    data: data
  }
}

// 登录接口
router.post('/login', async (ctx) => {
  let body = ctx.request.body
  let isLogin = false
  if (!ctx.request.body) {
    ctx.body = errRes('参数请求错误！')
  } else {
    console.log(333333, body.type)
    let user = db.get(`user.${body.type}`).value()
    if (!user) {
      ctx.body = errRes('暂不支持此类型用户登录！')
    } else {
      // 菜单列表
      let menuList = []
      for (let index = 0; index < user.length; index++) {
        const item = user[index]
        if (item.userName === body.account && item.pass === body.pass) {
          isLogin = true
          // 获取菜单list
          menuList = dbMenu.get([body.type]).value()
          // 写入数据库
          db.set('user.current', {
            name: item.userName,
            loginTime: new Date().getTime(),
            pageList: menuList
          }).write()
          break
        }
      }

      if (isLogin) {
        ctx.body = successRes('登录成功', menuList)
      } else {
        ctx.body = errRes('请检查账号和密码是否输入错误！')
      }
    }
  }
})

// 启用路由
app.use(router.routes())

// 开启后台服务
let server = app.listen('9081', function () {
  console.log('server running is http://localhost:9081')
})

// 数据库备份
function bakDB (filename) {
  console.log('开始备份数据库文件-------')
  let filepath = path.join(__dirname, `../../../static/db/${filename}.json`)
  let targetPath = path.join(__dirname, `../../../static/db_bak/${timeFormat(new Date().getTime(), 'yyyy-MM-dd-hh-mm-ss')}-${filename}.json`)
  fs.copyFile(filepath, targetPath, (err) => {
    if (err) {
      console.log('文件备份失败-------')
    } else {
      console.log('备份数据库文件成功-------')
    }
  })
}

// 定时器 用来备份数据 或者 干其他定时任务 时间设为半小时跑一次
setInterval(() => {
  bakDB('db')
  bakDB('menu')
}, 30 * 60 * 1000)
